package cn.fanqinghong.ymcc.service.impl;


import cn.fanqinghong.ymcc.doc.CourseDoc;
import cn.fanqinghong.ymcc.dto.CourseSearchDto;
import cn.fanqinghong.ymcc.esmapper.HighlightResultMapper;
import cn.fanqinghong.ymcc.repository.CourseESRepository;
import cn.fanqinghong.ymcc.result.PageList;
import cn.fanqinghong.ymcc.service.ISearchService;
import cn.fanqinghong.ymcc.vo.AggrPageList;
import cn.fanqinghong.ymcc.vo.AggrsBucket;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;

/**
 * FAN
 * 2022/7/24  15:35
 */
@Service
public class SearchServiceImpl implements ISearchService {
    @Autowired
    private CourseESRepository courseESRepository;

    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Autowired
    private HighlightResultMapper highlightResultMapper;
    @Override
    public void saveCourse(CourseDoc doc) {
        courseESRepository.save(doc);
    }

    @Override
    public void deleteCourse(CourseDoc doc) {
        courseESRepository.deleteById(doc.getId());
    }

    @Override
    public PageList<CourseDoc> search(CourseSearchDto doc) {
        //用来封装查询条件
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
        //封装排序规则
        setSortRuler(doc, builder);

        //封装分页的参数 从0开始 使用P挨个Request.of来封装当前页，和当前页总条数
        builder.withPageable(PageRequest.of(doc.getPage()-1,doc.getRows()));
        //封装查询条件
        BoolQueryBuilder boolQueryBuilder = getBoolQueryBuilder(doc);


        // 1.设置高亮样式 2.处理映射结果，准备映射器 3.使用映射处理高亮结果
        builder.withHighlightFields(new HighlightBuilder.Field("name")
                .preTags("<font style = 'color:red'>").postTags("</font>"));

        //实现聚合查询
        //设置聚合查询规则
        setEsQueryRuler(builder);


        //封装所有的查询条件
        builder.withQuery(boolQueryBuilder);
        //搜索es
        NativeSearchQuery build = builder.build();
        /*Page<CourseDoc> page = courseESRepository.search(build);*/
        //查询es，并且使用映射器处理高亮结果
        AggregatedPage<CourseDoc> page = elasticsearchRestTemplate.queryForPage(build, CourseDoc.class, highlightResultMapper);

        //处理聚合查询的结果
        HashMap<String, List<AggrsBucket>> aggResult = getStringListHashMap(page);


        //返回数据封装成PageList返回，总条数和内容
        return new AggrPageList<CourseDoc>(page.getTotalElements(),page.getContent(), aggResult);
    }

    /**
     * 设置聚合查询规则
     * @param builder
     */
    private void setEsQueryRuler(NativeSearchQueryBuilder builder) {
        //根据是否收费进行分类
        builder.addAggregation(AggregationBuilders.terms("aggsChargeName").field("chargeName"));
        //根据等级分类
        builder.addAggregation(AggregationBuilders.terms("aggsGradeName").field("gradeName"));
    }

    /**
     * 封装查询条件
     * @param doc
     * @return
     */
    private BoolQueryBuilder getBoolQueryBuilder(CourseSearchDto doc) {
        //封装分页查询条件
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //DSL查询
        if (doc.getKeyword()!=null) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("name", doc.getKeyword()));
        }
        //DSL过滤
        //课程类型id
        if (doc.getCourseTypeId()!=null){
            boolQueryBuilder.filter(QueryBuilders.termQuery("courseTypeId", doc.getCourseTypeId()));
        }
        //是否免费
        if (doc.getChargeName() != null){
            boolQueryBuilder.filter(QueryBuilders.termQuery("chargeName", doc.getChargeName()));
        }
        //等级名字
        if (doc.getGradeName() != null){
            boolQueryBuilder.filter(QueryBuilders.termQuery("gradeName", doc.getGradeName()));
        }
        //最小值
        if (doc.getPriceMin() != null){
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(doc.getPriceMin()));
        }
        //最大值
        if (doc.getPriceMax() != null){
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").lte(doc.getPriceMax()));
        }
        return boolQueryBuilder;
    }

    /**
     * 封装排序条件
     * @param doc
     * @param builder
     */
    private void setSortRuler(CourseSearchDto doc, NativeSearchQueryBuilder builder) {
        //封装排序的条件
        if (StringUtils.hasLength(doc.getSortField())){
            String sortFiled = null;
            switch (doc.getSortField().toLowerCase()){
                case "xl" : sortFiled = "saleCount";break;
                case "jg" : sortFiled = "price";break;
                case "xp" : sortFiled = "onlineTime";break;
                case "pl" : sortFiled = "commentCount";break;
                case "rq" : sortFiled = "viewCount";break;
                default:break;
            }
            //如果上面的排序条件部位nul才会进行倒排或者正排
            if (sortFiled != null) {
                if (doc.getSortType() != null) {
                    //使用忽略大小写的比较方法，判断排序类型，倒叙还是正排
                    SortOrder sortOrder = doc.getSortType().equalsIgnoreCase("ASC") ? SortOrder.ASC : SortOrder.DESC;
                    builder.withSort(new FieldSortBuilder(sortFiled).order(sortOrder));
                }
            }
        }
    }

    /**
     * 处理聚合查询的结果
     * @param page
     * @return
     */
    private HashMap<String, List<AggrsBucket>> getStringListHashMap(AggregatedPage<CourseDoc> page) {
        //创建一个map用来返回数据
        HashMap<String, List<AggrsBucket>> aggResult = new HashMap<>();
        //聚合结果处理
//        Map<String, Aggregation> aggregationMap = page.getAggregations().asMap();
        page.getAggregations().asMap().entrySet().forEach(e->{
            //使用设置的聚合查询的条件名用来做map集合的key
            String key = e.getKey();
            //得到聚合查询的值
            ParsedStringTerms value = (ParsedStringTerms) e.getValue();
            //遍历聚合查询的值，包装到List<AggrsBucket> 数组中
            List<AggrsBucket> aggrsBuckets = value.getBuckets().stream().map(bucket -> {
                //使用bucket中的key和dou_count封装到AggrsBucket中
                return new AggrsBucket(bucket.getKeyAsString(), bucket.getDocCount());
            }).collect(Collectors.toList());
            //给map中设置值
            aggResult.put(key,aggrsBuckets);
        });


        /*//完整写法
        //获得es中的数据转为map
        Map<String, Aggregation> aggregationMap = page.getAggregations().asMap();
        //得到es聚合查询的结果
        ParsedStringTerms aggsChargeName = (ParsedStringTerms) aggregationMap.get("aggsChargeName");
        ParsedStringTerms aggsGradeName = (ParsedStringTerms) aggregationMap.get("aggsGradeName");
        //创建list数组用来装去不同key的值
        ArrayList<AggrsBucket> aggrsBucketCharges = new ArrayList<>();
        ArrayList<AggrsBucket> aggrsBucketGrades = new ArrayList<>();
        //遍历聚合查询的结果
        aggsChargeName.getBuckets().forEach(bucket -> {
            String keyAsString = bucket.getKeyAsString();
            Long docCount = bucket.getDocCount();
            AggrsBucket aggrsBucket = new AggrsBucket(keyAsString,docCount);
            aggrsBucketCharges.add(aggrsBucket);
        });
        aggsGradeName.getBuckets().forEach(bucket -> {
            String keyAsString = bucket.getKeyAsString();
            Long docCount = bucket.getDocCount();
            AggrsBucket aggrsBucket = new AggrsBucket(keyAsString,docCount);
            aggrsBucketGrades.add(aggrsBucket);
        });
        aggResult.put("aggsChargeName",aggrsBucketCharges);
        aggResult.put("aggsGradeName",aggrsBucketGrades);*/
        return aggResult;
    }
}
